<style scoped>
.s94_upload{
  --s94-upload-size: 72px;
  --s94-upload-background: #f7f8fa;
  --s94-upload-border-radius: 0;
  --s94-upload-padding: var(--van-padding-xs);
  --s94-upload-upload-icon-size: 24px;
  position: relative;
  display: inline-block;
}
.inner{display: flex;flex-wrap: wrap;align-items: flex-start;}
.preview_row{position: relative;cursor: pointer;margin: 0 var(--s94-upload-padding) var(--s94-upload-padding) 0;}
.preview_row:last-child{margin: 0;}
.preview_row_delete{position: absolute;top: 0;right: 0;width: 14px;height: 14px;background: rgba(0,0,0,.7);border-radius: 0 0 0 12px;font-size: 14px;color: #fff;}
.preview_row_delete .van-icon{position: absolute;top: 0;right: 0;transform: scale(.7) translate(10%,-10%);}
.file_unit{
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  object-fit: contain;
  width: var(--s94-upload-size);
  height: var(--s94-upload-size);
  background: var(--s94-upload-background);
  border-radius: var(--s94-upload-border-radius);
  color: #646566;
}
.file_name{width: 100%;text-align: center;font-size: 12px;max-height: 2.2em;overflow: hidden;line-height: 1.1;}
.file_icon{font-size: 20px;}
.upload_icon{font-size: var(--s94-upload-upload-icon-size);color: #dcdee0;}
.mask{color: #fff;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  display: flex;
  position: absolute;
  top: 0px;
  bottom: 0px;
  left: 0px;
  right: 0px;
  background: rgba(50,50,50,.88);
  border-radius: var(--s94-upload-border-radius);
}
.status_icon{font-size: 22px;}
.preview_image_popup{max-width: 100%;width: 100%;height: 100%;}
</style>
<template>
  <div class="s94_upload">
    <input ref="input_file" style="display: none" type="file" :accept="accept" @change="file_change">
    <div class="inner">
      <div v-for="(row,i) in value_arr" class="preview_row" @click="click_row(row, i)">
        <img-referrer v-if="row.type==='image'" class="file_unit" :src="_url_static(row.url||row.object_url)" @error="img_error(row)"></img-referrer>
        <div v-else class="file_unit">
          <van-icon class="file_icon" name="description" />
          <p class="file_name">{{row.name}}</p>
        </div>
        <div v-show="row.status=='uploading'||row.status=='failed'" class="mask">
          <van-loading v-if="row.status=='uploading'" class="status_icon" color="#fff" size="24px" />
          <van-icon v-else class="status_icon" name="close" />
          <p class="file_name">{{row.message}}</p>
        </div>
        <div v-if="!is_info && deletable" class="preview_row_delete"
             aria-label="删除" @click.stop="del_row(i)">
          <van-icon name="cross" />
        </div>
      </div>
      <div v-if="!is_info && (!max||value_arr.length<max)" class="preview_row" @click="$refs.input_file.click()">
        <div class="upload file_unit">
          <s94_icon class="upload_icon" :value="upload_icon" ></s94_icon>
        </div>
      </div>
    </div>

    <van-popup class="preview_image_popup" v-model:show="preview_image_show" style="background: none;">
      <div class="flex" style="width: 100%;height: 100%;" @click="preview_image_show=false">
        <img-referrer :src="_url_static(preview_item.url||preview_item.object_url)" style="width: 100%;" draggable="false"/>
      </div>
    </van-popup>
  </div>
</template>

<script>
import s94 from "s94-js";
import {_url_static, img_handle} from "@/js/s94_tool.js";
import {file_upload} from "@/js/api_tool.js";
import tool from "@/js/tool.js";
import {msg} from "@/js/vant_tool.js";
import ImgReferrer from "@/components/widget/ImgReferrer.vue";

let mime_map = {
  'txt':'text/plain',
  'css':'text/css',
  'js':'text/javascript',
  'html':'text/html',

  'jpg':'image/jpeg',
  'jpeg':'image/jpeg',
  'png':'image/png',
  'gif':'image/gif',
  'bmp':'image/bmp',
  'webp':'image/webp',
  'tiff':'image/tiff',
  'ico':'image/vnd.microsoft.icon',
  'svg':'image/svg+xml',
  'psd':'image/image/vnd.adobe.photoshop',

  'mp3': 'audio/mpeg',
  'wav': 'audio/wav',
  'ogg': 'audio/ogg',
  'mid': 'audio/midi',
  'midi': 'audio/midi',
  'ra': 'audio/x-pn-realaudio',

  'mp4': 'video/mp4',
  'avi': 'video/x-msvideo',
  'mov': 'video/quicktime',

  'json': 'application/json',
  'xml': 'application/xml',
  'pdf': 'application/pdf',
  'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'docm': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'dotx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'dotm': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',

  'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'xlsb': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'xltx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'xltm': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',

  'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'pptm': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'potx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'potm': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',

  'zip':'application/zip',
  'gzip':'application/x-gzip',
  'tar':'application/x-tar',
  'rar':'application/x-rar-compressed',
  '7z':'application/x-7z-compressed',
}
function read_before(file){
  //图片改变分辨率
  if (!~['image/jpeg','image/png'].indexOf(file.type)) return file;
  return new Promise((resolve)=>{
    img_handle(file, {width:2000, height:2000, fit:'scale-down', blank:'cut', to:'blob'}).then(blob=>{
      file = new File([blob], file.name, {type: blob.type});
      resolve(file);
    })
  })
}
/**文件读取后的操作，一般为把文件上传到服务器，然后修改传入的file_item
 * @param {{status:String, message:String, url:String, type:String, name:String}} file_item
 * @returns {Promise<*>}
 */
function read_after(file_item){
  file_item.status = 'uploading';
  file_item.message  = '上传中...';
  return new Promise((ok, fail)=>{
    file_upload(file_item.file).then((data)=>{
      file_item.status = '';
      file_item.url = data.url;
      file_item.message  = '上传成功';
      ok(file_item);
    }).catch((e)=>{
      let err_message = e.message;
      file_item.status = 'failed';
      file_item.message  = err_message;
      fail(e);
    });
  })
}
function delete_before(file_item){
  return true;
}
export default {
  components: {ImgReferrer},
  props: {
    modelValue: [String,Number],
    config: [String,Object],
    is_info: Boolean,
    max: [String,Number],
    upload_icon: {type: String, default:'vant-icon photograph'},//上传图标
    reupload: Boolean, //覆盖上传
    preview_image: {type: Boolean, default: true}, //全屏预览图片
    deletable: {type: Boolean, default: true}, //显示删除按钮

    read_before: {type: Function, default:read_before}, //文件读取前的回调，接受选择的文件对象File，返回File|true|false，返回false会中断上传
    read_after: {type: Function, default:read_after}, //文件读取后的回调，接受file_item对象，返回file_item
    delete_before: {type: Function, default:delete_before}, //删除前的回调，接受file_item对象，返回true|false
  },
  emits: ['update:modelValue','change'],
  data(){
    return {
      need_display: true, //定义当modelValue变动时是否需要渲染，用来阻止当前组件变动 modelValue 后重新渲染
      value_arr: [],
      config_arr: [],
      accept: '',

      this_file_item: false,
      upload_ing: false,
      max_index: 0,
      preview_image_show: false,
      preview_item: {},
    }
  },
  methods: {
    _url_static,
    img_error(file_item){
      file_item.type = '';
    },
    click_row(file_item, i){
      if (this.reupload && !this.is_info){
        //覆盖上传
        this.$refs.input_file.click();
        this.this_file_item = file_item;
      }else if (this.preview_image && file_item.type === 'image'){
        //预览
        tool.preview_img({url: file_item.url||file_item.object_url});
      }
    },
    async file_handle(file){
      let file_item = this.this_file_item || {index:this.max_index, file:file, status:'uploading', message:'上传中...', object_url:'', url:'', type:'', name:''};
      let is_push = !this.this_file_item;
      this.this_file_item = false;
      file_item.file = file;
      file_item.name = file.name;
      if (typeof this.read_before === 'function'){
        file = await this.read_before(file_item.file);
        if (!file) return false;
        if (file instanceof Blob) file_item.file = file;
      }
      file_item.object_url = URL.createObjectURL(file_item.file);
      file_item.type = file.type.split('/').shift();
      if (is_push) {
        this.value_arr.push(file_item);
        this.max_index++;
      }

      file_item.status = '';
      file_item.message = '上传成功';
      if (typeof this.read_after === 'function'){
        file_item = await this.read_after(file_item);
      }
      this.change();
      return file_item;
    },
    file_change(e){
      if (this.upload_ing) return msg('上传中，请稍后！');
      this.upload_ing = true;
      let dom = e.target;
      let file = dom.files[0];
      if (!file) return msg('选择文件不存在');
      dom.value = '';
      Promise.resolve(this.file_handle(file)).then(file_item=>{
        this.upload_ing = false;
      }).catch((e)=>{
        this.upload_ing = false;
      })
    },
    display(){
      this.config_arr = this.config ? s94.to_string(this.config).split(',') : [];
      let accept = [];
      let def_type = false;
      this.config_arr.forEach(type=>{
        let mime = ~type.indexOf('/') ? type : mime_map[type] || "application/"+type;
        let cache_type = mime.split('/').shift();
        if (def_type===false || cache_type===def_type){
          def_type = cache_type;
        }else{
          def_type = '';
        }
        accept.push(mime);
      })
      this.accept = accept.join(",");

      this.value_arr = [];
      s94.to_string(this.modelValue).split(',').forEach((url,index)=>{
        if (!url) return ;
        let type = this.type_of_url(url, def_type);
        let name = this.name_or_url(url);
        let valur_row = {index, url, type, name};
        this.value_arr.push(valur_row);
      })
      this.max_index = this.value_arr.length;
    },
    name_or_url(url){
      let ms = url.match(/\/([^\/\?]+)($|\?)/);
      if (!ms) return url;
      return ms[1];
    },
    type_of_url(url, def){
      // image/video/audio/def
      let ms = url.match(/\.(\w+)($|\?)/);
      if (!ms) return def;
      let suffix = ms[1];
      if (~['jpg','jpeg','png','gif'].indexOf(suffix)) return 'image';
      if (~['mp4','avi','wmv','mpg','mpeg','mov','rm','ram','mov','swf','flv'].indexOf(suffix)) return 'video';
      if (~['mp3','wma','wav','amr','aac'].indexOf(suffix)) return 'audio';
      return def;
    },
    del_row(i){
      let file_item = this.value_arr[i];
      Promise.resolve(this.delete_before(file_item)).then(ok=>{
        if (ok){
          this.value_arr.splice(i,1);
          this.change();
        }
      })
    },
    change(){
      let value_arr = [];
      this.value_arr.forEach(row=>{
        if (row.url) value_arr.push(row.url);
      })
      let val = value_arr.length ? value_arr.join(",") : "";
      this.need_display = false;
      setTimeout(()=>{this.need_display=true},10)
      this.$emit('update:modelValue', val);
      this.$emit('change', val);
    }
  },
  watch: {
    config(v){
      this.display();
    },
    modelValue(){
      if (this.need_display) this.display();
    },
  },
  computed: {},
  mounted() {
    this.display();
  }
}
</script>